//----BEGIN LICENSE----//
/*****************************************************************\
** prevalentLite - sqlite3 persistence object library 
**
** Copyright (C) 2008 Philip R. Schuchardt
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
*****************************************************************/
//----END LICENSE----//
/* --------------------------------------------------------------------------------------------
        Access the sqlite3 database for saving and loading data.

        By: Philip Schuchradt - 2008-10-25
-------------------------------------------------------------------------------------------------*/

#ifndef plDATABASE_H
#define plDATABASE_H

//System includes STL
#include <stdlib.h>
#include <list>
#include <vector>
#include <string>
#include <sstream>

//Debug includes
#include <iostream>

//Sqlite includes
#include <sqlite3.h>

//Our includes
class plObject;
class plData;
class plMemory;
#include "plFactory.h"

class plDatabase {

    //So we can access AddObject
    friend class plObject;
    friend class plMemory;

public:
    //Constructor
    plDatabase();

    //Destructor
    virtual ~plDatabase();

    //Opens a new database
    int Open(const std::string& filename);

    //Saves to a file
    int CopyTo(const std::string& filename);

    //Closes the currently open database
    int Close();

    //Sets the data for an object - Returns true if saved
    bool SetData(plData* data);

    //Gets the data for the object
    bool GetData(plData* data) const;

    //Starts a new transaction
    void Begin(const std::string& name = std::string());

    //Ends a transaction
    void End(const std::string& name = std::string());

    //Gets the sqlite database - For plList classes only - Don't use you will break everything!!!
    sqlite3* GetSqliteDB();

    //Puts and index on the tables column
    //void Index(plData& data);

    //////////////////////////////////////////////////////////
    /*!
        \brief Tries to get a pointer based on it's database id

        \param id The database memory id to lookup the object
        \return T* reterns the current pointer or greatest a new object it the data.  Returns NULL if object can't be created from the id

        */
    //////////////////////////////////////////////////////////
    template <class T>
            T* GetObject(sqlite_int64 id) {

        //If id is valid
        if (id <= 0) {
            std::cout << "GetPointer - Invalid id: " << id << std::endl;
            return NULL;
        } //end if(id < 0)

        //Release - SQL string
        //QTextStream(&sqlSelect) << "SELECT Address FROM plMemory WHERE ObjectId =" << id;

        //Type checking - DEBUGGING
        ReadOnly(true);
        T* castCheckObject = new T();
        std::string objectClassName = castCheckObject->_GetClassName();
        std::stringstream stream;
        stream << "SELECT Address, ClassName FROM plMemory WHERE ObjectId=" << id;
        //std::cout << "GetObject sql: " << (const char*)sqlSelect.toAscii() << std::endl;
        ReadOnly(false);

        //Release
        //QTextStream(&sqlSelect) << "SELECT Address FROM plMemory WHERE ObjectId =" << id;

        sqlite3_stmt* statement;
        int tableError = sqlite3_prepare_v2(Database, stream.str().c_str(), -1, &statement, NULL);

        //If sql prepare was okay
        if (tableError == SQLITE_OK) {
            tableError = sqlite3_step(statement);
            if (tableError == SQLITE_ROW) {
                sqlite_int64 address = sqlite3_column_int64(statement, 0);

                //If address is allocated already ie the object is in memory
                if (address) {

                    //Debugging - Type checking
                    //std::cout << "Found object: " << address << " Type: " << (const char*)objectClassName.toAscii() << std::endl;

                    //Scary cast
                    sqlite3_finalize(statement);
                    return (T*)address;
                } else {
                    const char* className = (const char*)sqlite3_column_text(statement, 1);

                    //std::cout << "Creating object" << std::endl;

                    ReadOnly(true);

                    //We need to allocate the object
                    T* object = static_cast<T*>(ObjectFactory.New(className));
                    object->SetId(id);

                    UpdateMemory(object, object);

                    ReadOnly(false);

                    sqlite3_finalize(statement);
                    return object;

                } //if (address)
            } //if(tableError == SQLITE_ROW)
        } //if(tableError == SQLITE_OK)

        sqlite3_finalize(statement);
        return NULL;

    } //end GetPointer(sqlite_int64 id)

    ///////////////////////////////////////////////////////////
    /*!
          \brief Gets all the T objects from the database

          Returns all the active and inactive T objects.  This function knows how to deal with inheridance and will return correctly initialized objects.

          Example:
        \code
        MyObj* obj1 = new MyObj(); //Subclass of plObject
        MyObj* obj2 = new MyObjSubA(); //Subclass of MyObj
        MyObj* obj3 = new MyObjSubB(); //Subclass of MyObjSubA

        std::list<MyObj*> listObjs;
        database->GetAllObjects<MyObj>(listObjs);
        //listObjs.at(0) == obj1
        //listObjs.at(1) == obj2
        //listObjs.at(2) == obj3

        std::list<MyObjSubA*> listObjSubAs;
        database->GetAllObjects<MyObjSubA>(listObjsSubAs);
        //listObjsSubAs.at(0) == obj2;
        //listObjsSubAs.at(1) == obj3;

        std::list<MyObjSubB*> listObjSubBs;
        database->GetAllObjects<MyObjSubB>(listObjsSubBs);
        //listObjsSubBs.at(0) == obj3;

        \endcode

          \param objects The database will populate this list with T objects
        */
    ///////////////////////////////////////////////////////////


    ///////////////////////////////////////////////////////////
    /*!
          \brief Gets all the T objects from the database

          Returns all the active and inactive T objects.  This function knows how to deal with inheridance and will return correctly initialized objects.          

          \param objects The database will populate this vector with T objects
        */
    ///////////////////////////////////////////////////////////
    template <class T>
            void GetAllObjects(std::vector<T*>& objects) {

        ReadOnly(true);

        //The select statement for all the
        T* object = new T();
        std::stringstream stream;
        stream << "SELECT Id from " << object->_GetClassName();
        delete object;

        ReadOnly(false);

        //Where all the objects will be stored
        //std::list<T*>* objects = new std::list<T*>();

        //Go through all ids and create objects
        sqlite3_stmt* statement;
        int tableError = sqlite3_prepare_v2(Database, stream.str().c_str(), -1, &statement, NULL);

        //If sql prepar was okay
        if (tableError == SQLITE_OK) {
            //The current id value
            sqlite_int64 id;

            //Go through all the rows in the table
            while (sqlite3_step(statement) == SQLITE_ROW) {
                id = sqlite3_column_int64(statement, 0);
                //std::cout << "Id:" << id << std::endl;
                object = GetObject<T>(id);
                objects.push_back(object);
            } //while(sqlite3_step(statement) == SQLITE_ROW)
        }// end if(tableError == SQLITE_OK)

        sqlite3_finalize(statement);

    }

    //If true, disables object being add to the database
    void ReadOnly(bool readonly);

    //True if the database is in a readonly state
    bool IsReadOnly();

    //Gets the current static database
    static plDatabase* GetDatabase();

protected:
    //Creates a memory object in the database
    void CreateMemoryObject(plMemory* object);

    //Adds the object to the database
    void AddObject(plObject* object);

    //Adds the object to memory and sets it's Id
    void AddToMemory(plObject* object);

    //Updates memory table with the object's memory address
    void UpdateMemory(plObject* object, void* realAddress);

    //Delete from the object from it's table
    void DeleteObject(plObject* object);

    //Delete the object from memory table
    void RemoveFromMemory(plObject* object);

    //Resets the memory - ie. Sets all the address to NULL - Returns sql error
    int ResetMemory();

    //Alters the table to have the column
    int AddColumn(plData* data);

    //Tries to find the object with the neariest pointer - Returns null
    //This function is very dangerous!  Bacisilly it guess the object based only memory address
    plObject* GetNearestObject(void* data) const;

    //Called in SetData and GetData if the data wasn't setup correctely
    void SetupError(const char* className, plData* data) const;

    //! A pointer to the global static
    static plDatabase* GlobalDatabase;

    //! A pointer to a open sqlite3 database
    sqlite3* Database;

    //! Creates objects form string names
    plFactory ObjectFactory;

    //! Stops objects from being put into the database or setting there data
    bool CantWrite;

    //! True if the database is in a transaction
    bool InTransaction;

    //! The current transaction name
    std::string TransactionName;

    //! The currently opened file's filename
    std::string OpenedFilename;

};

#endif //plDatabase




